// <copyright file="DungeonGenerator.UniqueMethods.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>Dungeon level generator.</summary>

// License:

// Product: RPGRoguelikeEngine .Net
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using TenneySoftware.Engine;

namespace TenneySoftware.LevelGenerator.DungeonGenerator {
    /// <content>
    /// This file contains the Unque (Not defined within ILevelGenerator interface) methods for the generator.
    /// </content>
    public partial class Dungeon {
        /// <summary>
        /// Passes parameters to unique methods within this generator through the ILevelSettings interface. Methods available are:
        /// 0 - SetStructureWeight(int structure, int weight)
        /// 1 - EqualizeStructureWeights()
        /// 2 - AdjustStructureWeight(int structure, double weight)
        /// methodCalls usage: new object[][]{new object[]{0, 5, 10}}
        /// Will call SetStructureWeights with 5 as structure and 10 as the value to set.
        /// Structure Weight codes:
        /// 1 - Hall, 2 - LongHall, 3 - WanderingHall, 4 - Room, 5 - SmallRoom, 6 - LargeRoom, 7 - HugeRoom, 8 - CircularRoom,
        /// 9 - CircularSmallRoom, 10 - CircularLargeRoom, 11 - CircularHugeRoom, 12 - Cave, 13 - LargeCave.
        /// </summary>
        /// <param name="methodCalls">Jagged array of objects with the method code at the first position, and the arguments
        /// to pass in the remaining position.</param>
        public void UseUniqueMethods(object[][] methodCalls) {
            if (methodCalls.GetLength(0) > 3) {
                throw new ArgumentException("Too many options in array. Recheck your options.");
            }

            foreach (object[] arg in methodCalls) {
                switch ((int)arg[0]) {
                    case 0:
                        if (arg[1] is int && arg[2] is int) {
                            this.SetStructureWeight((int)arg[1], (int)arg[2]);
                        } else {
                            throw new ArgumentException("First argument needs to be a "
                                                        + typeof(int).ToString() + " and second argument needs to be a" +
                                                        typeof(int).ToString() + " for SetStructureWeight(int structure, int weight) call.");
                        }

                        break;
                    case 1:
                        this.EqualizeStructureWeights();
                        break;
                    case 2:
                        if (arg[1] is int && arg[2] is double) {
                            this.AdjustStructureWeight((int)arg[1], (int)arg[2]);
                        } else {
                            throw new ArgumentException("First argument needs to be a "
                                                        + typeof(int).ToString() + " and second argument needs to be a" +
                                                        typeof(double).ToString() + " for AdjustStructureWeight(int structure, double weight) call.");
                        }

                        break;
                }
            }
        }

        /// <summary>
        /// Retrieves a list of methods that are unique to this generator and not to any other.
        /// </summary>
        /// <returns>An array of GeneratorData objects with information about the unique methods within this generator.</returns>
        public GeneratorData[] GetUniqueMethodInformation() {
            GeneratorData[] result = new GeneratorData[3];
            result[0] = new GeneratorData(
                0,
                "SetStructureWeight",
                "Sets a specified structure weight's value.",
                new string[] { typeof(int).ToString(), typeof(int).ToString() });
            result[1] = new GeneratorData(
                1,
                "EqualizeStructureWeights",
                "Evaluates the values currently in the structure weights list and adjusts them so that they equal 100.",
                new string[] { null });
            result[2] = new GeneratorData(
                2,
                "AdjustStructureWeight",
                "Adjusts the structure weights so that after the value for the type is given, the remaining values will " +
                " adjust equally to maintain a total of 100.",
                new string[] { typeof(int).ToString(), typeof(double).ToString() });
            return result;
        }

        /// <summary>
        /// Adjusts the structure weights so that after the value for the type is given, the remaining values will adjust
        /// equally to maintain a total of 100. If the current total for the weights are less than 100, then the adjustments
        /// will only occur after the total reaches 100.
        /// </summary>
        /// <param name="type">Type of structure to alter.</param>
        /// <param name="value">Decimal value to set the given type to.</param>
        /// <returns>Returns the difference from the original value of the given type to the new value.</returns>
        public double AdjustStructureWeight(StructureType type, double value) {
            double original;
            double difference;
            if (this.structureWeights.ContainsKey(type)) {
                original = this.structureWeights[type];
                this.structureWeights[type] = value;
            } else {
                original = 0;
                this.structureWeights.Add(type, value);
            }
            
            difference = value - original;
            this.EqualizeStructureWeights(type);
            return difference;
        }

        /// <summary>
        /// Equalizes the Structure weights so that the values added together will equal 100.
        /// Any values that are 0 (turned off) will be skipped.
        /// </summary>
        public void EqualizeStructureWeights() {
            this.EqualizeStructureWeights(StructureType.Void);
        }
        
        /// <summary>
        /// Sets the weight value to a specified structure type. The weight value must be between 0 and 100.
        /// If the structure type does not currently exist in the list, then it will be created with the given value.
        /// </summary>
        /// <param name="type">Structure type to set.</param>
        /// <param name="weight">Weight value to set the structure type to.</param>
        public void SetStructureWeight(StructureType type, int weight) {
            if (weight > 100 || weight < 0) {
                throw new ValueOutsideOfRangeException("Weight is not within range (0 - 100). (" +
                                                     weight.ToString(System.Globalization.CultureInfo.CurrentCulture) + ")");
            } else {
                if (this.structureWeights.ContainsKey(type)) {
                    this.structureWeights[type] = weight;
                } else {
                    this.structureWeights.Add(type, weight);
                }
                
                this.SetName("Custom");
            }
        }

        /// <summary>
        /// Adjusts the structure weights so that after the value for the type is given, the remaining values will adjust
        /// equally to maintain a total of 100. If the current total for the weights are less than 100, then the adjustments
        /// will only occur after the total reaches 100.
        /// </summary>
        /// <param name="structure">Structure to adjust.</param>
        /// <param name="value">Weight to set.</param>
        private void AdjustStructureWeight(int structure, double value) {
            this.AdjustStructureWeight((StructureType)structure, value);
        }

        /// <summary>
        /// Equalizes the Structure weights so that the values added together will equal 100.
        /// Any values that are 0 (turned off) will be skipped.
        /// This method will keep the given structure type unaltered.
        /// </summary>
        /// <param name="type">Structure type to leave unaltered.</param>
        private void EqualizeStructureWeights(StructureType type) {
            double structureTotal;
            double adjustmentValue;
            double adjuster;
            int adjustments = 0;
            List<StructureType> skipZeros = new List<StructureType>();
            structureTotal = this.TotalWeights();
            foreach (KeyValuePair<StructureType, double> kvp in this.StructureWeightsList) {
                if (kvp.Key != type && kvp.Value > 0) {
                    adjustments++;
                } else {
                    if (kvp.Value == 0) {
                        skipZeros.Add(kvp.Key);
                    }
                }
            }
            
            adjustmentValue = (100 - structureTotal) / adjustments;
            foreach (string check in StructureType.GetNames(typeof(StructureType))) {
                StructureType checkType = (StructureType)Enum.Parse(typeof(StructureType), check);
                if (this.StructureWeightsList.ContainsKey(checkType) && checkType != type && !skipZeros.Contains(checkType)) {
                    adjuster = this.StructureWeightsList[checkType] + adjustmentValue;
                    if (adjuster <= 0) {
                        adjuster = 0;
                    }
                    
                    this.StructureWeightsList[checkType] = adjuster;
                }
            }
            
            this.CheckWeights();
        }

        /// <summary>
        /// Sets the weight of a structure.
        /// </summary>
        /// <param name="structure">Structure to set weight to.</param>
        /// <param name="weight">Weight to set.</param>
        private void SetStructureWeight(int structure, int weight) {
            this.SetStructureWeight((StructureType)structure, weight);
        }
    }
}